SAMS Programming Library 

For TurboForth V1.2.1:1

Written by Mark Wills 

Introduction

The SAMS memory expansion for the TI-99/4A is a useful, though woefully underutilised device. Despite offering a full megabyte of memory, literally only a handful of programs exist that can take advantage of it. TurboForth has built in support for the SAMS unit, allowing memory pages to be paged in and out. However, it has no inherent programming support for the SAMS unit. This paper discusses a tiny support library written in high-level Forth which allows colon definitions to be hosted in SAMS paged memory. The dictionary entries for the colon definitions continue to be defined and linked in normal memory, meaning that a colon definition can be found, ticked, and executed regardless of the SAMS bank in which it is hosted, and regardless of whether it is paged into memory or not. Colon definitions can be written (in any bank) which reference code in any bank, with complete disregard to the mechanics of paging memory in and out; the paging and un-paging of banks is handled completely transparently by the support library.

Declaring the Number of Banks

Before the library can be used, the number of SAMS that you wish to use must be declared. 248 banks are available, numbered from 0 to 247, giving up to 992K or usable memory. The remaining banks of the SAMS constitute the standard 32K of the TI. The declaration is made with the BANKS word. 

    BANKS ( n -- )

As can be seen from the above stack comment, BANKS requires a value on the stack. For example: 

    20 BANKS

This actually reserves some memory (in the standard 24K segment, which is not paged) to track the HERE pointers for each bank. HERE in Forth parlance gives the address of the next datum to be compiled to memory. 

Writing Banked Colon Definitions

It is very easy to write a colon definition in a particular bank. Simply use the word SETBANK to set the active bank. All colon definitions will be defined in the selected bank until another bank is selected: 

    SETBANK ( n -- )

For example:

    3 SETBANK

Now, any colon definition defined will be defined in bank 3: 

    : test ( -- a b c ) 1 2 3 ;

This definition will be compiled into bank 3.

Note that after each colon definition is defined a report is given on the amount of free memory in the bank. A bank is 4K (4096 bytes) in size. Care should be taken not to allow code to spill outside of the 4096 byte limit. Switch to a new bank if you think the next definition will not fit in the available space.

Disabling Banked Compilation

To disable banked compilation and resume compilation in normal un-banked memory simply give the value -1 to SETBANK as follows:

    -1 SETBANK

At this point, compilation resumes in normal un-paged memory.

Variables, Values and Constants

Variables, values, and constants continue to be compiled into normal un-paged memory regardless of the active bank setting. Due to their small size theres no particular advantage to putting them in banked memory, and the overhead of a bank switch to obtain their value would not make sense.

Calling Code in Different Banks

It is possible for a word in any bank to refer to a word in any other bank. No special action is required on the part of the programmer. For example:


     4 BANKS
     
     0 SETBANK
     : TOM ( -- ) . Tom  ;
     
     1 SETBANK
     : DICK ( -- ) . Dick  ;
     
     2 SETBANK
     : HARRY ( -- ) . Harry  ;
     
     3 SETBANK
     : TDH ( -- ) CR TOM DICK HARRY CR ;

Here, code in bank 3 refers to code in banks 0, 1 and 2. Although they all occupy the same physical address in the TIs memory map (the 4K region at >3000 to >3FFF) the banking is all handled automatically.

Nesting of Banked Definitions

Banked words/definitions can call words in other banks, which in turn call words in other banks, which in turn call words in other banks etc. This is achieved through the use of a bank stack implemented in the support library which pages the banks in and out as required. No action is required on the part of the programmer.

For example:

    0 SETBANK
    : TOM   ( -- )      ." TOM "  ;

    1 SETBANK
    : DICK  ( -- ) TOM  ." DICK " ;

    2 SETBANK
    : HARRY ( -- ) DICK ." HARRY" ;

    3 SETBANK
    : GO    ( -- ) CR HARRY CR ." FINISHED!!" CR ; -1 SETBANK
    GO 

In the above example, GO, which is in bank 3, immediately calls HARRY in bank 2. HARRY immediately calls DICK in bank 1. DICK immediately calls TOM in bank 0, which displays TOM then exits back to DICK in bank 1, which displays DICK, then exits back to HARRY in bank 2 which displays HARRY, then exits to GO in bank 3, which displays FINISHED!! Then exits. Note that the bank nesting and un-nesting is handled completely transparently.

Ticking and Executing Banked Words

The dictionary entries for banked words are actually compiled into normal, standard, un-banked memory. Only the executable code for a colon definition is compiled into the appropriate bank. This was a deliberate design decision as it allows words to be found with FIND (the word in the Forth system that searches the dictionary for words), allows them to be ticked with  and [] and also allows them to be compiled with COMPILE and/or [COMPILE] and executed with EXECUTE with no special treatment required whatsoever.

The Code

The code for the SAMS programming support library is presented below. As written below, it occupies 694 bytes of memory (0.6K). It is a testament to the power and flexibility of Forth that such functionality can be grafted into the Forth system, whilst being implemented in Forth itself.



\ Introductory Outline
\ The code within this file implements the SAMS programming
\ library. The code in this file is entirely self-contained
\ and relies on no external code or libraries.



\ Exports
\ The "public" interfaces of this library are:
\  BANKS ( #banks -- ) declares the number of SAMS 4K pages to 
\  to available to the programmer.
\
\ SETBANK ( bank|-1 -- ) sets the active 4K page that will
\ receive compiled code. If a value of -1 is supplied then
\ banked compilation shall be disabled and all compiled shall
\ be compiled in standard, un-paged dictionary space.



\ Environmental Dependancies
\ This code is depandant upon the presence of either an AMS
\ memory card (256KB) or a SAMS memory card (1034KB) in the 
\ peripheral expansion box of the TI-99/4A.
\ Due to the above, the code is de-facto depandant on a
\ Texas Instruments TI-99/4A computer.
\ Additionally, the code uses the word >MAP which is defined as
\ follows:
\ >MAP ( bank address -- )
\ where bank is the 4K memory page, and address is a memory 
\ address within the 64K memory map of the TMS9900 CPU. address
\ must sit on a 4K boundary. The AMS card has 256/4=64 memory
\ banks. The SAMS card has 1024/4=256 memory banks.
\ The word >MAP is included as standard in the TurboForth
\ dictionary at power-up.
\
\ The code also relies on the ability (in the TurboForth 
\ interpreter) to specify a hexadecimal number with a preceding
\ dollar sign. For example: $10 refers the decimal value 16.



\ Revision History
\ Version 1.00 - First Release - 10th March 2015



\ Includes
\   None.


DECIMAL \ force decimal mode, just in case



\ Constants and Variables
\ Constants
\  PAGEF9. The default power-up condition for TurboForth is for
\  memory bank F9 hex to be paged into the CPU memory map at
\  address 3000 hex. The constant PAGEF9 defines this page:
$F9 CONSTANT PAGEF9

\ no. of cells to reserve for bank stack:
20 CONSTANT BANK_STACK  

\ address of the end of the paged 4K in CPU RAM:
$4000 CONSTANT END_PAGE

\ address of start of 4K memory page in addressable CPU RAM
$3000 CONSTANT BANKED_RAM

\ Variables
\  None.



\ Buffers
\ _bankstk ("bank stack"). An array used to implement a bank
\ stack. The bank stack is used to support the nested execution
\ of paged code.
create _bnkstk BANK_STACK cells allot


\ ** ENVIRONMENTAL DEPANDANCY: ** 
\ When TurboForth is in its "default" configuration the second
\ half of the 8K memory expansion (>3000) is set to SAMS page
\ >F9. The following line of code initialises the first page of
\ the bank stack to page F9. This ensures that when executing
\ nested bank/pages, when it all unwinds, the default page is 
\ swapped into memory.
PAGEF9 _bnkstk ! \ force first entry on bank stack to $F9



\ Values 
_bnkstk value _bsp    \ pointer into bank stack
-1 value _bank        \ current bank
0 value _heres        \ points to list of HEREs for each bank
0 value _nhere        \ "normal" here
0 value _maxBank      \ holds the highest legal bank number



\ Colon Definitions
: >bank ( bank# -- )
( G: push bank# to bank stack )
  [ 1 cells ] literal  +to _bsp  \ advance bank stack index
  dup _bsp !                     \ update bank stack pointer
  BANKED_RAM >MAP                \ and map in bank# to $3000
;

: bank> ( -- ) 
( G: pop bank from bank stack )
  [ -1 cells ] literal +to _bsp  \ decrement bank stack index
  _bsp @                         \ fetch the bank to map in
  BANKED_RAM >MAP                \ map it in at address $3000
;

: banks ( n -- )  
( G: reserve space for here pointers for n banks.)
( G: Note that this reserves space in the dictionary. )
  here to _heres             \ address of start of HEREs table
  dup to _maxBank            \ set max legal bank number 
  dup 0 do BANKED_RAM , loop \ init "here" for each bank 
  cr 4 * n>s type ." K of banked memory reserved." cr
;
  
: b: ( bank# -- )
( G: begin compiling a banked definition in bank bank# )
  _bank -1 <> if
        \ banked memory compilation
        :                       \ create dictionary entry
        compile lit _bank ,     \ compile bank no. as literal 
        compile >bank           \ payload for LIT
        
        \ compile branch to first free address in bank#
        compile branch
        _bank cells _heres + @ dup ,
        here to _nhere          \ save "normal here"
        h !                     \ set h to _bank's "here"
        _bank BANKED_RAM >MAP   \ map in the appropriate bank
        \ at this point, the compiler's "here" has been
        \ adjusted to compile into banked memory.
  else
    \ standard dictionary compilation
    :
  then
;


: _bfree ( -- ) 
( G: determine free memory in the bank )
  END_PAGE  _bank cells _heres + @ -  \ compute free bytes
  . ." bytes free." cr
;
  
  
: ;b ( -- )
( G: end banked compilation )
  \ at this point we're compiling into banked memory...
  compile branch  _nhere , \ compile branch back to normal mem.
  here  _bank cells _heres +  ! \ update here for bank
  _bfree                        \ report free memory 
  _nhere h !                    \ restore h to "normal" memory
  \ we're now compiling in normal dictionary space...
  compile bank>                 \ compile "pop" bank
  [compile] ;                   \ compile exit code
;
  
  
: : ( -- )
( G: banked / non-banked compilation )
( G: Note that this is a re-definition of the standard : word)
  _bank -1 = if : else b: then
;
  

: setBank ( bank# -- )
( G: sets the bank number that will receive colon definitions)
  dup -1 _maxBank within if
      \ legal bank number 
      to _bank              \ set active compilation bank#
      _bank -1 <> if
        cr ." Bank " _bank . ." is now active. " 
        _bfree
      else
        cr ." Compiling to standard 32K memory." cr
      then
   else
      true abort" Illegal bank number specified"
   then
;


: ; ( -- )
( G: redefine ; so that it compiles associated bank un-winding)
( G: bank un-winding is compiled into the current definition )
( G: by ;b )
_bank -1 = if [compile] ; else ;b then ; immediate 
